package fr.emse.tatiana.replayable.analysis;


import java.util.HashMap;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;
import java.util.Map.Entry;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorReference;
import org.eclipse.ui.PlatformUI;

import fr.emse.tatiana.ApplicationActionBarAdvisor;
import fr.emse.tatiana.display.BadDisplayFile;
import fr.emse.tatiana.display.DisplayFileReader;
import fr.emse.tatiana.display.DisplayFileWriter;
import fr.emse.tatiana.replayable.Anchor;
import fr.emse.tatiana.replayable.IArtefactEditor;
import fr.emse.tatiana.replayable.IReplayableModel;
import fr.emse.tatiana.replayable.ReplayableEvent;
import fr.emse.tatiana.replayable.SynchronisationEvent;
import fr.emse.tatiana.replayable.tabulardisplay.IReplayableEditor;
import fr.emse.tatiana.graphical.DisplayRule;
import fr.emse.tatiana.graphical.DisplayRulesParser;
import fr.emse.tatiana.utils.AutocompleteComboBoxCellEditor;

public class GraphAnalysis extends AbstractAnalysis implements IReplayableModel, IGraphAnalysis {
	
	public static int id = 0;
	public final static String DISPLAY_TYPE = "fr.emse.tatiana.graphanalysis";
	public final static int facetcount = 1;
	
	protected HashMap<Anchor, Rectangle> positions;
	protected HashMap<Anchor, String> texts;
	protected List<Link> links;
	protected Integer nullvalue;
	protected DisplayRulesParser rulessaver;
	protected Vector<IAnalysis> analyses;
	protected HashMap<Anchor, Long> begintimes;
	protected HashMap<Anchor, Long> endtimes;
	protected int defwidth = 100;
	protected int defheight = 40;
	
	
	public GraphAnalysis(String name, IProject p) {
		super(name,p);
	}

	public GraphAnalysis(IFile file) throws BadDisplayFile {
		super(file);
	}
	
	public void setAnalyses(Vector<IAnalysis> analyses) {
		this.analyses = analyses;
	}
	
	@Override
	protected void doInit() { // init so it's present during call to super()
		positions = new HashMap<Anchor, Rectangle>();
		texts = new HashMap<Anchor, String>();
		begintimes = new HashMap<Anchor, Long>();
		endtimes = new HashMap<Anchor, Long>();
		links = new Vector<Link>();
		nullvalue = new Integer(0);
		rulessaver = new DisplayRulesParser(project.getFolder("analyses"));
		
	}
	
	@Override
	protected void parseFile(DisplayFileReader reader) throws BadDisplayFile {
		if (!DISPLAY_TYPE.equals(reader.getType())) {
			throw new BadDisplayFile(BadDisplayFile.WRONGFILETYPE +" expected:"+DISPLAY_TYPE);
		}
		for (boolean notdone = reader.selectFirstItem();notdone;notdone = reader.selectNextItem()) {
			String type = reader.getStringValue("type", BadDisplayFile.MISSINGFIELD);
			if (type.equals("anchor")) {
				Anchor anchor = reader.getAnchorValue("anchor", BadDisplayFile.MISSINGFIELD);
				int x = reader.getIntValue("x", BadDisplayFile.MISSINGFIELD);
				int y = reader.getIntValue("y", BadDisplayFile.MISSINGFIELD);
				int width = reader.getIntValue("width", BadDisplayFile.MISSINGFIELD);
				int height = reader.getIntValue("height", BadDisplayFile.MISSINGFIELD);
				String text = reader.getStringValue("text", BadDisplayFile.MISSINGFIELD);
				try {
					setTime(anchor, reader.getLongValue("time", ""),
							reader.getLongValue("endtime", ""));
				} catch (BadDisplayFile bdf) {
					// code copied from below
					AnalysisEvent reference = new AnalysisEvent(this,anchor);
					IEditorReference[] editors = PlatformUI.getWorkbench().getWorkbenchWindows()[0].getActivePage().getEditorReferences();
					for (int i=0; i<editors.length; i++) {
						IEditorPart ed = editors[i].getEditor(false);
						if (ed instanceof IReplayableEditor) {
							IReplayableModel m = ((IReplayableEditor) ed).getModel();
							Object[] events = m.getAnalysedEvents(reference);
							if (events.length!=0) {
								Object event = events[0];
								if (text.startsWith("box")) {
									text = m.getFacetText(event, chooseContentFacet(m));
								}
								setTime(anchor, m.getBeginTime(event),
										m.getLastTime(event));
								break;
							}
						}
					}
				}
				values.put(anchor, new Integer(1));
				positions.put(anchor, new Rectangle(x,y,width,height));
				texts.put(anchor, text);
			} else if (type.equals("link")) {
				Anchor source = reader.getAnchorValue("source-anchor", BadDisplayFile.MISSINGFIELD);
				Anchor target = reader.getAnchorValue("target-anchor", BadDisplayFile.MISSINGFIELD);
				String desc = reader.getStringValue("text", BadDisplayFile.MISSINGFIELD);
				int linktype = reader.getIntValue("link-type", BadDisplayFile.MISSINGFIELD);
				links.add(new Link(source,target,linktype,desc,this));
			}
		}
		saved = true;
		rulessaver.setFile("."+this.getName()+".scorelayout");
		rulessaver.openFile();
		fireAnalysisEvent(new AnalysisEvent(this));
	}


	@Override
	protected Object doGetNullValue() {
		return nullvalue;
	}

	@Override
	protected String doGetStringValue(Object ret) {
		int i  = (Integer) ret;
		if (i == 0) return "No";
		else return "Yes";
	}

	@Override
	public CellEditor getChooser(Composite parent) {
		AutocompleteComboBoxCellEditor chooser = new AutocompleteComboBoxCellEditor(parent, new String[]{""}, SWT.READ_ONLY);;
			       
		updateCellChooserItems(chooser);
	    
		return chooser;
	}

	public void updateCellChooserItems(AutocompleteComboBoxCellEditor chooser) {
		if (chooser == null)
			return;
		Vector<String> names = new Vector<String>();
		names.add("No");
		names.add("Yes");
		chooser.setItems(names.toArray(new String[]{}));
	}

	@Override
	protected boolean saveToAnalysisFile(IFile file) {
		DisplayFileWriter writer = new DisplayFileWriter(file.getLocation().toString(),DISPLAY_TYPE);

		for (Anchor anchor : values.keySet()) {
			Object v = values.get(anchor);
			if (hasValueForAnchor(anchor)) {
				Rectangle position = getPosition(anchor);
				writer.newItem();
			    writer.newInfo("type", "", "anchor");
				writer.newInfoFromString("anchor", anchor.toString());
				writer.newInfo("text", "", texts.get(anchor));
				writer.newInfo("x","", position.x);
				writer.newInfo("y","", position.y);
				writer.newInfo("width","", position.width);
				writer.newInfo("height","", position.height);
				if (begintimes.get(anchor)!=null) {
					writer.newInfo("time", "", begintimes.get(anchor));
					writer.newInfo("endtime", "", endtimes.get(anchor));
				}
			}
		}
		
		for (Link l : links) {
			writer.newItem();
		    writer.newInfo("type", "", "link");
			writer.newInfoFromString("source-anchor", l.getSource().toString());
			writer.newInfoFromString("target-anchor", l.getTarget().toString());
			writer.newInfo("link-type","", l.getType());
			writer.newInfo("text", "", l.getDesc());
		}
		writer.close();
		
		rulessaver.doSave("."+this.getName()+".scorelayout");
		
		saved = true;
		return true;
	}

	
	public String getEditorID() {
		return GraphAnalysisEditor.ID;
	}
	


	public void setPosition(Anchor anchor, Rectangle layout) {
		positions.put(anchor, layout);
		saved = false;
		fireAnalysisEvent(new AnalysisEvent(this,anchor));
	}

	
	public Rectangle getPosition(Anchor anchor) {
		return positions.get(anchor);
	}
	
	public int chooseContentFacet(IReplayableModel m) {
		//todo put this in IReplayableModel as method
		Vector<Integer> lengths = new Vector<Integer>();
		for (int i=0; i<m.getFacetCount() && !m.isAnalysisFacet(i); i++) {
			lengths.add(i, 0);
		}
		
		for (int j=0; j<m.getEventCount(); j++) {
			for (int i=0; i<m.getFacetCount() && !m.isAnalysisFacet(i); i++) {
				String ftext = m.getFacetText(m.getEvent(j), i);
				if (ftext!=null) {
					lengths.set(i, lengths.get(i)+ftext.length());
				}
			}
		}
		int ret = 0;
		int max = 0;
		for (int i=0; i<m.getFacetCount() && !m.isAnalysisFacet(i); i++) {
			int ctot = lengths.get(i);
			if (ctot > max) {
				ret = i;
				max = ctot;
			}
		}
		return ret;
	}
	
	private Object cevent;
	private IReplayableModel cmodel;
	// sets cevent and cmodel to the event and replayable represented by anchor
	private void findEventByAnchor(Anchor anchor) {
		cevent = null;
		cmodel = null;
		AnalysisEvent reference = new AnalysisEvent(this,anchor);
		IEditorReference[] editors = PlatformUI.getWorkbench().getWorkbenchWindows()[0].getActivePage().getEditorReferences();
		for (int i=0; i<editors.length; i++) {
			IEditorPart ed = editors[i].getEditor(false);
			if (ed instanceof IReplayableEditor) {
				IReplayableModel m = ((IReplayableEditor) ed).getModel();
				Object[] events = m.getAnalysedEvents(reference);
				if (events.length!=0) {
					cevent = events[0];
					cmodel = m;
					break;
				}
			}
		}
	}
	
	@Override
	public void setValue(Anchor anchor, Object value) {
		findEventByAnchor(anchor);
		setValue(anchor, value, new Rectangle(10,20,defwidth,defheight));
	}
	
	
	public void setValue(Anchor anchor, Object value, Rectangle r) {
		if (value !=null && !value.equals(doGetNullValue()) && !positions.containsKey(anchor)) {
			positions.put(anchor,r);
			// TODO replace code above for finding text by this
			if (cevent != null) {
				setTime(anchor, cmodel.getBeginTime(cevent),
						cmodel.getLastTime(cevent));
			
				texts.put(anchor, cmodel.getFacetText(cevent, chooseContentFacet(cmodel)));
			} else {
				texts.put(anchor,"box"+ (++id));
			}
		} else if (value.equals(doGetNullValue())) {
			for (Link link: getSourceConnections(anchor)) {
				link.delete();
			}
			for (Link link: getTargetConnections(anchor)) {
				link.delete();
			}
		}
		super.setValue(anchor, value);
	}

	private void setTime(Anchor anchor, long beginTime, long endTime) {
		begintimes.put(anchor, beginTime);
		endtimes.put(anchor, endTime);
	}

	public void setText(Anchor anchor, String text) {
		texts.put(anchor, text);
		saved = false;
		fireAnalysisEvent(new AnalysisEvent(this,anchor));
	}
	
	public String getText(Anchor anchor) {
		return texts.get(anchor);
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.analysis.IGraphAnalysis#getSourceConnections(fr.emse.tatiana.replayable.Anchor)
	 */
	public List<Link> getSourceConnections(Anchor anchor) {
		List<Link> srclist = new Vector<Link>();
		for (Link link: links) {
			if (link.getSource().equals(anchor)) {
				srclist.add(link);
			}
		}
		return srclist;
	}
	
	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.analysis.IGraphAnalysis#getTargetConnections(fr.emse.tatiana.replayable.Anchor)
	 */
	public List<Link> getTargetConnections(Anchor anchor) {
		List<Link> srclist = new Vector<Link>();
		for (Link link: links) {
			if (link.getTarget().equals(anchor)) {
				srclist.add(link);
			}
		}
		return srclist;
	}

	public void createLink(Anchor source, Anchor target, int type,
			String desc) {
		links.add(new Link(source,target,type, desc,this));
		saved = false;
		fireAnalysisEvent(new AnalysisEvent(this, source));
		//TODO fire correct kind of event
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.analysis.IGraphAnalysis#removeLink(fr.emse.tatiana.replayable.analysis.Link)
	 */
	public void removeLink(Link link) {
		links.remove(link);
		saved = false;
		fireAnalysisEvent(new AnalysisEvent(this, link.getSource()));
	}
	
	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.analysis.IGraphAnalysis#updateLink(fr.emse.tatiana.replayable.analysis.Link)
	 */
	public void updateLink(Link link) {
		saved = false;
		fireAnalysisEvent(new AnalysisEvent(this, link.getSource()));
		// TODO fire correct kind of event
		
	}

	public List<Object> anchoredEvents(SynchronisationEvent event) {
		List<Object> ret = new Vector<Object>();
		for (Anchor anchor : event.getAnchors()) {
			if (hasValueForAnchor(anchor)) {
				ret.add(anchor);
			}
		}
		return ret;
	}
	
	public void touchRules() {
		rulessaver.touch();
		saved = false;
	}
	
	public List<DisplayRule> getRules() {
		return rulessaver.getRules();
	}

	
	// TODO many things are left TODO here - means that analysis as a replayable isn't quite done
	public boolean canModifyColumn(int col) {
		// TODO Auto-generated method stub
		return false;
	}

	public List<Object> currentEvents(SynchronisationEvent event) {
		long begin = event.getBegin();
		long end = begin + event.getDuration();

		List<Object> ret = new Vector<Object>();
		ReplayableEvent last = (ReplayableEvent) lastEventBefore(event.getNow());

		for (Entry<Anchor,Long> entry : begintimes.entrySet()) {
			Anchor a = entry.getKey();
			Long eventbegin = entry.getValue();
			Long eventend = endtimes.get(a);
			if (eventbegin<=end && eventend>=begin) {
				ret.add(a);
			}
		}

		return ret;
	}

	public Object[] getAnalysedEvents(IAnalysis a) {
		return getAnalysedEvents(a.getAnchors());
	}

	public Object[] getAnalysedEvents(AnalysisEvent evt) {
		Set<Anchor> anchors = new HashSet<Anchor>();
		anchors.addAll(evt.getAnchors());
		return getAnalysedEvents(anchors);
	}
	
	public Object[] getAnalysedEvents(Set<Anchor> anchors) {
		Vector<Anchor> ret = new Vector<Anchor>();
		for (Anchor a: getAnchors()) {
			if (anchors.contains(a)) {
				ret.add(a);
			}
		}
		return ret.toArray(new Anchor[]{});
	}

	public List<IAnalysis> getAnalyses() {
		return analyses;
	}

	public Anchor getAnchor(Object o) {
		return (Anchor) o;
	}

	public Color getBackground(Object element, int col) {
		Anchor anchor = (Anchor) element;
		if (col < facetcount) {
			return null;
		} else {
			IAnalysis a = analyses.get(col-facetcount);
			return a.getBackground(anchor);
		}
	}
	
	public boolean isTimeKnown(Object event) {
		return begintimes.containsKey(event);
	}

	public long getBeginTime(Object firstElement) {
		Long ret = begintimes.get(firstElement);
		return (ret==null) ? 0 : ret;
	}

	public CellEditor getCellEditor(int col, Composite c) {
		// TODO Auto-generated method stub
		return null;
	}

	public Object getEvent(int index) {
		return new Vector<Anchor>(getAnchors()).get(index); // TODO this will fail miserably if the order of elements is not deterministic 
	}

	public int getEventCount() {
		return getAnchors().size();
	}

	public List<Long> getEventTimestamps() {
		List<Long> ret = new Vector<Long>(begintimes.values());
		Collections.sort(ret);
		return ret;
	}

	public Object getFacet(Object elem, int col) {
		Anchor anchor = (Anchor) elem;
		if (col < facetcount) {
			return getText(anchor);
		} else {
			IAnalysis a = analyses.get(col-facetcount);
			return a.getValueForAnchor(anchor);
		}
	}

	public int getFacetCol(String name) {
		if (name == "text") {
			return 0;
		} else {
			for (int i=0; i<analyses.size(); i++) {
				if (analyses.get(i).getName().equals(name)) {
					return facetcount+i;
				}
			}
		}
		return -1;
	}

	public int getFacetCount() {
		return facetcount+analyses.size();
	}

	public int getFacetIdFor(IAnalysis a) {
		int ret = analyses.indexOf(a);
		if (ret < 0)
			return ret;
		else
			return facetcount + ret;
	}

	public String getFacetName(int col) {
		if (col<facetcount) {
			return "text";
		} else {
			return analyses.get(col-facetcount).getName(); // for analysis columns
		}
	}

	public String getFacetText(Object element, int col) {
		Anchor anchor = (Anchor) element;
		if (col<facetcount) {
			return getText(anchor);	
		} else {
			return analyses.get(col-facetcount).getTextValueForAnchor(anchor);
		}
	}

	public long getFirstTime() {
		// TODO Auto-generated method stub
		return 0;
	}

	public int getFontSize() {
		// TODO Auto-generated method stub
		return 0;
	}

	public Color getForeground(Object element, int columnIndex) {
		// TODO Auto-generated method stub
		return null;
	}

	public long getLastTime(Object object) {
		// TODO Auto-generated method stub
		return 0;
	}

	public long getLastTime() {
		// TODO Auto-generated method stub
		return 0;
	}

	public int getPreferredWidth(int col) {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean hasReplayableFile() {
		// TODO Auto-generated method stub
		return false;
	}

	public Object lastEventBefore(long time) {
		// TODO Auto-generated method stub
		return null;
	}

	public void setFacetValue(Object event, int col, Object value) {
		// TODO Auto-generated method stub
		
	}

	/* (non-Javadoc)
	 * @see fr.emse.tatiana.replayable.analysis.IGraphAnalysis#getLinks()
	 */
	public List<Link> getLinks() {
		return this.links;
	}

	public boolean isAnalysisFacet(int i) {
		return true;
	}

	public void setMultiple(Set<Anchor> anchors, Object value) {
		
		Vector<Anchor> sortedAnchors = new Vector<Anchor>();
		Vector<Long> tstmps = new Vector<Long>();
		for (Anchor anchor: anchors) {
			findEventByAnchor(anchor);
			long cts = cmodel.getBeginTime(cevent);
			int i=0;
			for (i=0;i<sortedAnchors.size();i++) {
				if (tstmps.get(i)>cts) break;
			}
			sortedAnchors.add(i,anchor);
			tstmps.add(i, cts);
		}
		
		int x = 20;
		HashMap<String,Integer> ys = new HashMap<String, Integer>();
		for (Anchor anchor: sortedAnchors) {
			findEventByAnchor(anchor);
			if (cevent!=null) {
				String user = cmodel.getFacetText(cevent,cmodel.getFacetCol("user"));
				int y = 0;
				if (ys.containsKey(user)) {
					y = ys.get(user);
				} else {
					y = 20+3*defheight*ys.size();
					ys.put(user, y);
				}
				setValue(anchor, value, new Rectangle(x,y,defwidth,defheight));
			} else {
				setValue(anchor, value);
			}
			x+= (20+defwidth);
		}	
	}

	@Override
	protected boolean populateApplyToSelectionMenuManager(final List sel,
			final IReplayableModel r, MenuManager mm) {
		final GraphAnalysis self = this;
		final Set<Anchor> anchors = new HashSet<Anchor>();
		for (Object o: sel) {
			anchors.add(r.getAnchor(o));
		}
		mm.add(new Action("Add to graph") {
			@Override
			public void run() {
				
				setMultiple(anchors, new Integer(1));
			}
		});
		mm.add(new Action("Remove from graph") {
			@Override
			public void run() {
				setMultiple(anchors, self.doGetNullValue());
			}
		});
        
        
		return true;
	}
	
}